#!/usr/bin/env node
const path = require('path')
const shell = require('shelljs')
const inquirer = require('inquirer')
const program = require('commander')

const logs = require('./utils/log')
const { getFileData, writeFileData } = require('./utils/common')

const globalConfigFile = '.config.json'

function sleep (t = 0) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, t)
    })
}

function getInputConfig() {
    return new Promise((resolve, reject) => {
        const data = {}
        const dataKeys = [
            {key: 'username', keyName: '用户名'},
            {key: 'password', keyName: '密码'},
            {key: 'email', keyName: 'Email'}
        ]
        const getConfig = (key, keyName) => {
            inquirer.prompt({
                type: 'input',
                name: key,
                message: `请输入（npm）${keyName}`,
                default: ''
            })
                .then(answers => {
                    if (answers && answers[key]) {
                        data[key] = answers[key]
                        const nextKeyOption = dataKeys.shift()
                        if (nextKeyOption) {
                            getConfig(nextKeyOption.key, nextKeyOption.keyName)
                        } else {
                            resolve(data)
                        }
                    } else {
                        getConfig(key, keyName)
                    }
                })
                .catch(reject)
        }
        const keyOption = dataKeys.shift()
        getConfig(keyOption.key, keyOption.keyName)
    })
}

async function deleteGlobalConfig () {
    const res = await writeFileData(globalConfigFile, '{}', __dirname)
    if (res) {
        logs.success('删除成功')
    } else {
        logs.error('删除失败')
    }
    return Promise.resolve(res)
}

async function updateGlobalConfig () {
    const data = await getInputConfig()
    const res = await writeFileData(globalConfigFile, JSON.stringify(data), __dirname)
    if (res) {
        logs.success('配置更新成功')
    } else {
        logs.error('配置更新失败')
    }
    return Promise.resolve(data)
}

async function getGlobalConfig(isVew = false) {
    return new Promise(async (resolve, reject) => {
        let data = await getFileData(globalConfigFile, __dirname)
        if (data) {
            data = eval(`(${data})`) || {}
        } else {
            data = {}
            await writeFileData(globalConfigFile, '{}', __dirname)
        }
        if (!data.username || !data.password || !data.email) {
            if (isVew !== true) {
                data = await updateGlobalConfig()
            }
        }
        resolve(data)
    })
}

async function getConfig(isGetGlobalConfig = true) {
    // 运行项目路径读取
    const PRESET_PATH = process.cwd()
    const file = 'npm.auto.publish.js'
    let data = await getFileData(file, PRESET_PATH)
    if (data) {
        data = require(path.join(PRESET_PATH, file)) || {}
    } else {
        data = {}
    }
    if (!data.publish) {
        data.publish = 'npm publish'
    }
    if (isGetGlobalConfig !== false) {
        if (!data.username || !data.password || !data.email) {
            const globalConfig = await getGlobalConfig()
            data = Object.assign({}, data, globalConfig)
        }
    }
    return Promise.resolve(data)
}

function hasIsLogged() {
    return new Promise(resolve => {
        logs.wait('npm 检查npm登录状态，请稍后...')
        shell.exec('npm whoami', (code) => {
            if (code === 0) {
                logs.success('npm 已登录' + '\n')
                resolve(true)
            } else {
                logs.error('npm 未登录，需重新登录' + '\n')
                resolve(false)
            }
        }, {silent: true})
    })
}

function npmLogin({ username, password, email } ={}) {
    return new Promise(async (resolve, reject) => {
        if (await hasIsLogged()) {
            await sleep(1000)
            resolve()
        } else {
            await sleep(1000)
            const inputType = {
                'Username:': username + '\n',
                'Password:': password + '\n',
                'Email: (this IS public)': email + '\n'
            }
            logs.wait('npm 开始登录')
            await sleep(1000)
            const child = shell.exec('npm login', {async: true})

            child.stdout.on('data', (chunk) => {
                const cmd = inputType[chunk.trim()]
                if (cmd) {
                    shell.echo(cmd)
                    child.stdin.write(cmd)
                } else {
                    if (/password.+authenticator.+/.test(chunk)) {
                        const input = () => {
                            inquirer.prompt({
                                type: 'input',
                                name: 'code',
                                message: '请输入npm安全验证码',
                                default: ''
                            })
                                .then((answers) => {
                                    if (answers && answers.code) {
                                        shell.echo(answers.code)
                                        child.stdin.write(answers.code + '\n')
                                    } else {
                                        input()
                                    }
                                })
                                .catch(e => {
                                    logs.error(e)
                                })
                        }
                        input()
                    } else if (/Logged/.test(chunk)) {
                        child.stdin.end()
                        resolve()
                    }
                }
            })
        }
    })
}

async function gitPush ({ git } = {}, commit = '') {
    if (git) {
        if (!commit || typeof commit !== 'string') {
            commit = 'commit'
        }
        git = git.replace(/\$\{commit\}/, commit)
        logs.line()
        logs.log(`git 配置`, git.yellow)
        await sleep(1000)
        if (!shell.which('git')) {
            logs.warning('git 未检测到git，请安装后重试')
            logs.error('git 推送取消')
            shell.exit(1)
            return
        }
        await sleep(600)
        logs.log(`git 准备推送`)
        await sleep(600)
        logs.wait(`git 开始推送`)

        shell.exec(git, (code) => {
            if (code === 0) {
                logs.success(`git 代码推送成功`)
            } else {
                logs.error(`git 代码推送失败`)
            }
        })
    }
}

async function publish (commit) {
    const config = await getConfig()
    npmLogin(config)
    .then(async () => {
        logs.success('npm 登录成功，准备推送')
        await sleep(1000)
        logs.wait('npm 开始推送，请稍后...')
        await sleep(1000)
        shell.exec(config.publish, (code) => {
            if (code === 0) {
                logs.line()
                logs.success('npm 推送成功')
                gitPush(config, commit)
            } else {
                logs.error('npm 推送失败')
            }
        })
    })
    .catch(e => {
        logs.error( e)
    })
}

async function operationConfig (config) {
    if (config === 'set') {
        const data = await updateGlobalConfig()
        logs.log('config', data)
    } else if (config === 'get') {
        const data = await getGlobalConfig(true)
        logs.log('config', data)
    } else if (config === 'remove') {
        await deleteGlobalConfig()
    }
}

function confirmPublish (commit) {
    const packageJson = require(path.join(process.cwd(), './package.json'))
    const name = `\n包名：${packageJson.name}\n版本：${packageJson.version}\n作者：${packageJson.author || '无'}`
    inquirer.prompt({
        type: "confirm",
        message: `${name}\n是否发布？`,
        name: "confirm",
    })
        .then(async answers => {
            if (answers.confirm) {
                logs.line()
                await publish(commit)
            }
        })
}

const configDescription = `set     设置或更新 npm 配置
get     获取查看 npm 诶之
remove  删除npm 配置
`

program
    .option('--config <configOperation>', configDescription)
    .option('--publish [commit]', '推送代码到npm commit在有git提交时作为git的commit')
    .option('--git [commit]', '推送代码到git')
    .action(async function (option, args) {
        if (option.config) {
            await operationConfig(option.config)
        } else if (option.publish) {
            await confirmPublish(option.publish)
        } else if (option.git) {
            const config = await getConfig(false)
            await gitPush(config, option.git)
        }
    })
    .parse(process.argv)
